package cn.lijiajia3515.cairo.auth.config.security;

import cn.hutool.core.util.RandomUtil;
import cn.lijiajia3515.cairo.auth.framework.AccountRepository;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.OAuth2JwkProperties;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.OAuth2ProviderProperties;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.server.CairoOAuth2AuthorizationServerService;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.server.authorization.CairoOAuth2TokenCustomizer;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.server.authorization.Jackson2RedisOAuth2AuthorizationService;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.server.authorization.RedisOAuth2AuthorizationConsentService;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.server.authorization.client.CairoRegisteredClientRepository;
import cn.lijiajia3515.cairo.captcha.CaptchaArgs;
import cn.lijiajia3515.cairo.captcha.image.ArithmeticImageCaptchaHandler;
import cn.lijiajia3515.cairo.captcha.image.CodeImageCaptchaHandler;
import cn.lijiajia3515.cairo.captcha.image.ImageCaptcha;
import cn.lijiajia3515.cairo.captcha.image.ImageCaptchaHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.util.List;

@Configuration(proxyBeanMethods = false)
public class OAuthServerBeanConfig {
	/**
	 * oauth2 client 配置
	 *
	 * @param mongoTemplate mongo
	 * @return oauth2 client repository
	 */
	@Bean
	@Primary
	public CairoRegisteredClientRepository registeredClientRepository(MongoTemplate mongoTemplate) {
		return new CairoRegisteredClientRepository(mongoTemplate);
	}

	// /**
	//  * @param namespace 命名空间
	//  * @param factory   工厂
	//  * @return 默认 redis 授权 服务
	//  */
	// @Bean
	// @ConditionalOnMissingBean
	// @Order(Ordered.HIGHEST_PRECEDENCE)
	// public DefaultRedisOAuth2AuthorizationService defaultRedisOAuth2AuthorizationService(@Value("${spring.application.name}") String namespace, RedisConnectionFactory factory) {
	// 	DefaultRedisOAuth2AuthorizationService service = new DefaultRedisOAuth2AuthorizationService(factory);
	// 	service.setNamespace(namespace);
	// 	return service;
	// }

	/**
	 * @param namespace     命名空间
	 * @param redisTemplate redis操作模板
	 * @return 默认 redis 授权 服务
	 */
	@Bean
	@ConditionalOnMissingBean
	public Jackson2RedisOAuth2AuthorizationService jackson2RedisOAuth2AuthorizationService(@Value("${spring.application.name}") String namespace, RedisTemplate<String, Object> redisTemplate) {
		Jackson2RedisOAuth2AuthorizationService service = new Jackson2RedisOAuth2AuthorizationService(redisTemplate);
		service.setNamespace(namespace);
		return service;
	}

	/**
	 * oauth2 authorization consent 配置
	 *
	 * @param namespace     namespace
	 * @param redisTemplate redis template
	 * @return redis oauth2 authorization content service
	 */
	@Bean
	public RedisOAuth2AuthorizationConsentService redisOAuth2AuthorizationConsentService(@Value("${spring.application.name}") String namespace, RedisTemplate<String, Object> redisTemplate) {
		RedisOAuth2AuthorizationConsentService bean = new RedisOAuth2AuthorizationConsentService(redisTemplate);
		bean.setNamespace(namespace);
		return bean;
	}


	/**
	 * oauth2 token 上下文自定义处理器
	 *
	 * @return jwt 编写器
	 */
	@Bean
	public CairoOAuth2TokenCustomizer cairoOAuth2TokenCustomizer() {
		return new CairoOAuth2TokenCustomizer();
	}

	//	@Bean
	//	public HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseHttpMessageConverter(ObjectMapper objectMapper) {
	//		return new OAuth2AccessTokenResponseHttpMessageConverter(objectMapper);
	//	}

	// -------------- .well-known/oauth-authorization-server 配置 --------------

	@Bean
	@RefreshScope
	@ConfigurationProperties("cairo.oauth2.provider")
	public OAuth2ProviderProperties oAuth2ProviderProperties() {
		return new OAuth2ProviderProperties();
	}

	/**
	 * 配置 oauth server 说明配置
	 *
	 * @param properties properties
	 * @return settings
	 */
	@Bean
	@ConditionalOnMissingBean
	public ProviderSettings providerSettings(OAuth2ProviderProperties properties) {
		return ProviderSettings.builder().issuer(properties.getIssuer()).build();
	}

	/**
	 * 启用 配置文件配置 jwk
	 *
	 * @return jwk 配置文件
	 */
	@Bean
	@RefreshScope
	@ConfigurationProperties(prefix = "cairo.oauth2.jwk")
	OAuth2JwkProperties oauth2JwkProperties() {
		return new OAuth2JwkProperties();
	}

	// -------------- password_code 端点 --------------
	@Bean
	CairoOAuth2AuthorizationServerService cairoOAuth2ClientAuthenticationService(MongoTemplate mongoTemplate,
																				 AccountRepository accountRepository,
																				 PasswordEncoder passwordEncoder) {
		return new CairoOAuth2AuthorizationServerService(mongoTemplate, accountRepository, passwordEncoder);
	}

	@Bean
	public ImageCaptchaHandler imageCaptchaHandler() {
		return new ImageCaptchaHandler() {
			private final List<? extends ImageCaptchaHandler> handlers = List.of(
				CodeImageCaptchaHandler.Singleton.NUM.handler,
				CodeImageCaptchaHandler.Singleton.CHAR.handler,
				CodeImageCaptchaHandler.Singleton.UPPER_CHAR.handler,
				CodeImageCaptchaHandler.Singleton.LOWER_CHAR.handler,
				new ArithmeticImageCaptchaHandler()
			);

			@Override
			public ImageCaptcha getCaptcha(CaptchaArgs args) {
				return handlers.get(RandomUtil.randomInt(handlers.size())).getCaptcha(args);
			}

			@Override
			protected Tuple2<String, String> getImageCaptchaStr(CaptchaArgs captchaArgs) {
				return Tuples.of("", "");
			}
		};
	}
}
